<!--     Super Collider game     -->
<!-- Copyright 2001 Scott Porter -->
<!-- scott@javascript-games.org  -->
<!-- Written to demonstrate the  -->
<!-- gamelib javascript game API -->

<!-- Please feel free to meddle  -->
<!-- with this game and improve  -->
<!-- upon it! If you think you've-->
<!-- done a good job extending it-->
<!-- then send it back to me and -->
<!-- I'll see about putting your -->
<!--     version on the site!    -->

<!-- DON'T just change the images-->
<!-- and put it up on another site->
<!-- (Yes, some people do this!) -->

<html>
<title>Super Collider: a demonstration of the gamelib library</title>
<head>

<style type="text/css">
.styleGameOver{font-family:Arial,Helvetica,sans-serif;font-size:30px;color:#ff0000}
.styleGetReady{font-family:Arial,Helvetica,sans-serif;font-size:30px;color:#00ff00}
.styleLevelClear{font-family:Arial,Helvetica,sans-serif;font-size:30px;color:#ffff00}
.styleDemo{font-family:Arial,Helvetica,sans-serif;font-size:16px;color:#ffffff}
.styleInfo{font-family:Arial,Helvetica,sans-serif;font-size:20px;color:#ffffff}
.styleDigits{font-family:Arial,Helvetica,sans-serif;font-size:24px;color:#ffffff}
a{font-family:Arial,Helvetica,sans-serif;color:#eeeeff;text-decoration:none}
a:hover{font-family:Arial,Helvetica,sans-serif;color:#00ff00;text-decoration:underline}

</style>

<script language="Javascript" src="../gamelib/gamelib_core.js"></script>
<script language="Javascript" src="../gamelib/gamelib_keyboard.js"></script>
<script language="Javascript" src="../gamelib/gamelib_sprites.js"></script>
<script language="Javascript" src="../gamelib/gamelib_interface.js"></script>

<script language="Javascript">

	// a bit of preloading here, so the browser doesn't get freaked out
	// loading moving images! ;-)

Gl_preloader("../images/i0.gif");
Gl_preloader("../images/xplode64x64.gif");
Gl_preloader("../images/collidertitle379x106.gif");
Gl_preloader("../images/pills.gif");
Gl_preloader("../images/horz80x16.gif");
Gl_preloader("../images/vert16x80.gif");
Gl_preloader("../images/ship360.gif");

	// we'll need a few global variables here...

	// first, the player's stuff. ship will be the player's sprite,
	// and the keys will hold keyboard objects to trap the cursor keys

var score=0;
var lives=2;
var level=0;
var speed=8;
var currentPillPosition;
var currentTotalPill;
var currentBarrier;
var demoKeyDown;
var demoKeyDownTime=0;

var ship;
var leftKey,rightKey,fireKey;

	// direction array holds the possible directions for the ship (degrees) There are 16
	// possible directions as the ship canvas image has 16 frames.

var direction=new Array(0,23,45,68,90,113,135,158,180,203,225,248,270,293,315,338);

	// myDir is the current direction for the player's ship.

var myDir=0;

	// trail array holds the last few hundred positions and rotations of the player's
	// ship. This will be used to position the following sprites as the convoy grows.
	// We'll use the array to hold Position objects to make things easier to work with.
	// trailIX will be a pointer for the position currently in use by the player ship.

var trail=new Array();
var trailIX=0;

	// convoy is the array of following sprites. convoyLength is number of convoy sprites.
	// Sure, we could just add sprites to convoy during the game, and know the length of convoy
	// but it's more efficient to fill convoy with sprites and hide the ones we don't need on
	// the screen.

var convoy=new Array();
var convoyLength=0;

	// now an array for the pills we are going to be collecting

var pill=new Array();

	// another array for the barrier objects

var barrier=new Array();

	// miscellaneous objects...

var txt=new Array();	// an array to hold layer objects for the text we'll have in the game
var backdrop;		// background layer
var scoreText;		// holds 'score' text layer
var livesText;		// holds 'lives' text layer
var gemsText;		// holds 'Red Gems' text layer
var scoreDigits;		// holds 'score digits' numeric display
var livesDigits;		// holds 'lives digits' numeric display
var gemsDigits;		// holds 'Red Gems left digits' numeric display
var demoText;		// text for demo screen
var demoTitle;		// title for demo screen
var myInterface;		// holds the interface object, which will contain the numeric displays

	// Level data. This specifies the positions of barriers and the positions of pills in
	// the levels. The pill positions are cyclic (is that a word?) so if there are more pills
	// to be collected than the positions defined for them, the next pill on the list will
	// loop back to the start again. Good for lazy people like moi!

	// levelbarrier array is filled with arrays describing the positions of barriers. These
	// are of the form x,y,type,speed,direction,minx,maxx,miny,maxy for position x,y, whether
	// barrier is horizontal (true) or vertical (false), speed and direction, then limits for
	// barrier, then whether the barrier bounces

var levelBarrier=new Array();

levelBarrier[0]=new Array(
	130,140,true,1,90,100,240,140,160,true,
	240,140,true,1,90,240,380,140,160,true,
	140,344,true,1,90,100,240,344,364,true,
	260,344,true,1,90,240,380,344,364,true
);

levelBarrier[1]=new Array(
	92,100,false,0,0,92,120,100,180,false,
	392,100,false,0,0,392,410,100,180,false,
	92,320,false,0,0,92,120,320,400,false,
	392,320,false,0,0,392,410,320,400,false,
	0,214,true,6,90,0,500,0,1000,true,
	500,254,true,5,270,0,500,0,1000,true

);

levelBarrier[2]=new Array(
	10,110,true,0,0,-100,1000,-100,1000,false,
	110,110,true,0,0,-100,1000,-100,1000,false,
	210,110,true,0,0,-100,1000,-100,1000,false,
	310,110,true,0,0,-100,1000,-100,1000,false,

	410,200,true,1,180,-100,1000,110,306,true,
	310,200,true,0,0,-100,1000,-100,1000,false,
	210,200,true,0,0,-100,1000,-100,1000,false,
	110,200,true,0,0,-100,1000,-100,1000,false,

	10,290,true,2,0,-100,1000,200,306,true,
	110,290,true,0,0,-100,1000,-100,1000,false,
	210,290,true,0,0,-100,1000,-100,1000,false,
	310,290,true,0,0,-100,1000,-100,1000,false,

	410,380,true,0,0,-100,1000,-100,1000,false,
	310,380,true,0,0,-100,1000,-100,1000,false,
	210,380,true,0,0,-100,1000,-100,1000,false,
	110,380,true,0,0,-100,1000,-100,1000,false

);
	// levelTotalPill holds array describing number of pills to collect before level is complete

var levelTotalPill=new Array(10,8,4);

	// levelPillPosition is array of arrays of x,y,x,y,x,y etc positions for pills in level

var levelPillPosition=new Array()

levelPillPosition[0]=new Array(100,100,150,100,200,100,250,100,400,400,300,400);
levelPillPosition[1]=new Array(40,40,434,434,40,434,434,40,234,234,40,234,434,234,234,234);
levelPillPosition[2]=new Array(40,40,434,434,40,434,434,40,234,234,40,234,434,234,234,234);

	// levelPillPositionIX is index into above array, for picking next pill position

var levelPillPositionIX=0;

	// levelStartPosition is the start position for the player in each level. This is an
	// array of x,y,rotation,x,y,rotation etc etc

var levelStartPosition=new Array(
	234,460,0,
	234,20,8,
	10,10,4
);

	// -- END of variables. Functions start here: -- //


// Position is an object used to hold a sprite position+rotation. No methods.

function Position(x,y,r){
	this.x=x;
	this.y=y;
	this.r=r;
}

function init(){

	var n;

	// first we'll set up our keys...

	leftKey=Kb_trapkey('LEFT');
	rightKey=Kb_trapkey('RIGHT');
	fireKey=Kb_trapkey('CTRL');

	// we'll make a layer for the background here

	backdrop=new Gl_layer(0,0,500,'');
	backdrop.setXlimits(0,500);
	backdrop.setYlimits(0,500);
	backdrop.setBgcolor('#000000');
	backdrop.resizeTo(500,500);
	backdrop.setZ(0);			// Make sure this layer is at the back!
	backdrop.show();

	// add a few pill sprites. These are for the player to pick up for points
	// but as each is picked up, the convoy becomes longer

	for(n=0;n<2;n++){
		pill[n]=new Sp_Sprite();
		pill[n].setImage("../images/pills.gif",27,27,2,4);
		pill[n].setXlimits(-50,500);
		pill[n].setYlimits(-50,500);
	}

	// now add our player sprite...

	ship=new Sp_Sprite();
	ship.setImage("../images/ship360.gif",32,32,17,1);
	ship.setXlimits(0,500);
	ship.setYlimits(0,500);

	// add the following sprites. These don't need to check for collisions,
	// or have speed since they're just placed at different positions and rotations
	// to follow the player's sprite. We'll make 100, that should be enough! :)
	// I'll only switch every 2nd one on here, as the player ship will check for
	// collisions with every sprite that's switched on. Since the space between the
	// members of the convoy is so close, the player won't be able to move through
	// the convoy! As well as this, I won't switch on the first 3 following sprites, as
	// 2 could be touching the player ship all the time, and it can't turn sharp enough
	// to hit the third.

	for(n=0;n<36;n++){
		convoy[n]=new Sp_Sprite();
		convoy[n].setImage("../images/ship360.gif",32,32,17,1);
		convoy[n].setXlimits(-50,500);
		convoy[n].setYlimits(-50,500);
		convoy[n].setFrame(0);
		convoy[n].switchOn();
	}

	// and add a few obstacles to the play area...

	for(n=0;n<16;n++){
		barrier[n]=new Sp_Sprite();
		barrier[n].setImage("../images/horz80x16.gif",80,16,1,1);
		barrier[n].setXlimits(-500,500);
		barrier[n].setYlimits(-500,500);
		barrier[n].moveTo(0,0);
		barrier[n].setFrame(0);
		barrier[n+16]=new Sp_Sprite();
		barrier[n+16].setImage("../images/vert16x80.gif",16,80,1,1);
		barrier[n+16].setXlimits(-500,500);
		barrier[n+16].setYlimits(-500,500);
		barrier[n+16].moveTo(-500,0);
		barrier[n+16].setFrame(0);
	}

	// Let's have some layers to display some scrolling text on the screen

	txt[0]=new Gl_layer(0,0,300,'<span class="styleGetReady">Get Ready!</span>');
	txt[1]=new Gl_layer(0,0,300,'<span class="styleLevelClear">Level Clear!</span>');
	txt[2]=new Gl_layer(0,0,300,'<span class="styleGameOver">Game Over</span>');

	// move them off screen for the moment...

	for(n=0;n<3;n++){
		txt[n].setXlimits(-500,1000);
		txt[n].setYlimits(-500,1000);
		txt[n].moveTo(-500,0);
	}

	// Make a sprite to use as an explosion...

	xplode=new Sp_Sprite();
	xplode.setImage("../images/xplode64x64.gif",64,64,1,16);
	xplode.setXlimits(-100,600);
	xplode.setYlimits(-100,600);
	xplode.setZ(400);

	// make 3 layers to show Score, Lives, and Red Gems Left text. we'll use the new Interface
	// object to actually display the digits. I could have used the new Label object in the interface
	// instead of using these layers, but I did this before I'd made that bit! I'll leave the
	// conversion as an "exercise for the reader" :-)

	scoreText=new Gl_layer(10,7,300,'<span class="styleInfo">Score</span>');
	livesText=new Gl_layer(423,7,300,'<span class="styleInfo">Lives</span>');
	gemsText=new Gl_layer(330,470,300,'<span class="styleInfo">Red Gems Left</span>');

	// another couple of layers for the demo mode. Again, I could have made life easier for
	// myself by using an interface here. They're always better for text/image content that
	// wont be moving about much, or not moving about at all.

	demoTitle=new Gl_layer(60,50,380,'<img src="../images/collidertitle379x106.gif" width=379 height=106>');
	demoTitle.hide();
	demoText=new Gl_layer(15,200,470,'<span class="styleDemo">The object of the game is to clear all the Red Gems to progress to the next level. As you pick up each Red Gem, another will appear until the level is cleared.<br><br>To earn bonus points, pick up Green Gems! As you pick up Gems, your Cyborg Worm (well, I didn\'t know what to call it :-) will grow, making life more difficult. Don\'t hit any of the barriers or your own body or you\'ll lose a life!<br><br>IE users, use the left and right cursor keys to steer, and the control key [CTRL] to start. Netscape users, use J/L for left right and Z to start!<br><br>This game was written by <a href="mailto:scott@javascript-games.org">Scott Porter</a> (c)2001 using the Gamelib dynamic API available from <a href="http://www.javascript-games.org/gamelib/" target="_blank">www.javascript-games.org</a></span>');
	demoText.setXlimits(-1000,1000);
	demoText.setBgcolor('#222222');
	demoText.hide();

	// now create an interface to hold the score, lives and gems left numeric displays.
	// this is much faster than rewriting layers.

	myInterface=new In_Interface(500,500,'../gamelib');

	gemsDigits=new In_NumericDisplay(472,474,1,0);	// numeric display with 1 digit, using font face 0
	livesDigits=new In_NumericDisplay(472,11,1,0);	// numeric display with 1 digit, using font face 0
	scoreDigits=new In_NumericDisplay(70,11,6,0);	// numeric display with 6 digits, using font face 0

	myInterface.add(gemsDigits);
	myInterface.add(livesDigits);
	myInterface.add(scoreDigits);

	myInterface.moveTo(0,0);				// move the interface onto the screen

	// start everything up!

	Gl_scrollbars("no");
	Gl_calibrate('realStart()',30);
}

function realStart(){
	Gl_start();
	startDemo();
}

function startDemo(){
	leftKey.pressed=false;
	rightKey.pressed=false;
	fireKey.pressed=false;	
	demoTitle.show()
	demoText.show();
	clearItems();
	backdrop.setBackground('../images/backgroundtile0.gif');
	myDir=0;
	ship.setZ(300);
	for(var n=0;n<convoy.length;n++)
		convoy[n].setZ(301+n);
	ship.setFrame(0);
	ship.moveTo(234,234);
	ship.setSpeed(8);
	convoyLength=10;
	ship.setXYdegs(0);
	ship.switchOn();
	demoKeyDownTime=0;
	demoKeyDown=0;
	Gl_hook('demoLoop()');
}

// just moves the convoy around randomly until the CNTRL key is pressed...

function demoLoop(){
	drawConvoy();
	if(demoKeyDownTime--==0){
		demoKeyDown=Math.floor(Math.random()*3)-1;
		demoKeyDownTime=Math.floor(Math.random()*20)+5;
	}
	myDir+=demoKeyDown;
	if(myDir<0)
		myDir+=16;
	else if(myDir>15)
		myDir-=16;
	ship.setXYdegs(direction[myDir]);
	ship.setFrame(myDir);
	if(fireKey.pressed)
		startGame();
}

function startGame(){
	Gl_unhook('demoLoop()');
	leftKey.pressed=false;
	rightKey.pressed=false;
	fireKey.pressed=false;	
	ship.setZ(3);
	demoTitle.hide();
	demoText.hide();
	score=0;
	lives=2;
	level=0;
	speed=5;
	startLevel();
}

function startLevel(){
	var n,v=20,h=0,c;

	clearItems();

	currentTotalPill=levelTotalPill[level];
	currentPillPosition=levelPillPosition[level];
	currentBarrier=levelBarrier[level];
	levelPillPositionIX=0;
	myDir=levelStartPosition[(level*3)+2];
	ship.moveTo(levelStartPosition[level*3],levelStartPosition[(level*3)+1]);
	ship.setFrame(myDir);
	ship.setXYdegs(direction[myDir]);
	ship.setSpeed(0);
	ship.setCollide(true);
	ship.switchOn();
	convoyLength=0;
	backdrop.setBackground('../images/backgroundtile'+(level%3)+'.gif');

// x,y,type,speed,direction,minx,maxx,miny,maxy

	for(n=0;n<currentBarrier.length;n+=10){
		c=(currentBarrier[n+2])?h++:v++;
		barrier[c].setXlimits(currentBarrier[n+5],currentBarrier[n+6]);
		barrier[c].setYlimits(currentBarrier[n+7],currentBarrier[n+8]);
		barrier[c].moveTo(currentBarrier[n],currentBarrier[n+1]);
		barrier[c].setSpeed(currentBarrier[n+3]);
		barrier[c].setXYdegs(currentBarrier[n+4]);
		barrier[c].bounces=currentBarrier[n+9];
		barrier[c].switchOn();
	}

	for(n=0;n<2;n++){
		if(n)
			pill[n].moveTo(Math.floor(Math.random()*400)+50,Math.floor(Math.random()*400)+50);
		else
			pill[n].moveTo(currentPillPosition[0],currentPillPosition[1]);
		pill[n].setFrame(n);
		pill[n].setAnimationSpeed(2,'Forward');
		pill[n].switchOn();
	}
	showLivesLeft();
	showGemsLeft();
	showScore();
	Gl_hook('preGameLoop()');
	txt[0].moveTo(500,230);
	Gl_ticker=0;
}

function showGemsLeft(){
	gemsDigits.setValue(currentTotalPill);
}

function showLivesLeft(){
	livesDigits.setValue(lives);
}

function showScore(){
	scoreDigits.setValue(score);
}

// clearItems just moves everything off screen, so we start with a clear playfield

function clearItems(){
	for(n=0;n<400;n++)
		trail[n]=new Position(-100,-100,0);
	ship.switchOff();
	xplode.switchOff();
	for(n=0;n<barrier.length;n++){
		barrier[n].setXlimits(-500,1000);
		barrier[n].moveTo(-500,0);
		barrier[n].setSpeed(0);
	}
	for(n=0;n<convoy.length;n++){
		convoy[n].moveTo(-500,0);
		convoy[n].setSpeed(0);
		convoy[n].setXYdegs(0);
		convoy[n].setFrame(0);
	}
	for(n=0;n<3;n++)
		convoy[n].switchOff();
	for(n=0;n<pill.length;n++)
		pill[n].moveTo(-500,0);
	for(n=0;n<txt.length;n++)
		txt[n].moveTo(-500,0);
}

// preGameLoop just scrolls Get Ready! across the screen to give player a chance
// to get ready for level. the text is held in the layer txt[0]

function preGameLoop(){
	txt[0].moveTo(txt[0].x-10,txt[0].y);
	if(txt[0].x<-400){
		ship.switchOn();
		ship.setSpeed(speed);
		Gl_unhook('preGameLoop()');
		Gl_hook('gameLoop()');
	}
}

// The main game loop. Checks keys, and checks collisions. I'm cheating a bit
// here and using the undocumented .index property of sprites/layers. The index
// of a sprite/layer is a unique numeric value which is set as it's created. The
// first layer/sprite is 0, the next 1 etc etc. Note the .index is completely unique,
// so no layer and sprite will share an index (ie, if you create a sprite, then a
// layer, then the sprite will have index of 0, the layer will have index 1.) I use
// this to quickly determine whether the player has hit a pill/barrier or convoy.
// So, if the sprite I've hit has an index under 11, I know it's a pill!!

function gameLoop(){
	checkKeys();
	drawConvoy()
	if(ship.hit)
		checkCollision()
}

// see which keys are being pressed at the moment and steer the player sprite accordingly

function checkKeys(){
	if(leftKey.pressed){
		if(--myDir==-1)
			myDir=15;
		ship.setXYdegs(direction[myDir]);
		ship.setFrame(myDir);
	}
	if(rightKey.pressed){
		if(++myDir==16)
			myDir=0;
		ship.setXYdegs(direction[myDir]);
		ship.setFrame(myDir);
	}
}

function drawConvoy(){
	var n,p;
	trail[trailIX]=new Position(ship.x,ship.y,myDir);
	for(n=1;n<convoyLength+1;n++){
		p=trailIX-(n<<2);
		if(p<0)
			p+=400;
		convoy[n-1].setFrame(trail[p].r);
		convoy[n-1].moveTo(trail[p].x,trail[p].y);
	}
	if(++trailIX==400)
		trailIX=0;
}

function checkCollision(){
	var n;
	if(ship.hit.index<4){
		score+=25;
		showScore();
		if(ship.hit.index==3)
			ship.hit.moveTo(Math.floor(Math.random()*400)+50,Math.floor(Math.random()*400)+50);
		else{
			if(--currentTotalPill==0){
				Gl_ticker=0;
				Gl_unhook('gameLoop()');
				for(n=0;n<3;n++)
					convoy[n].switchOn();
				ship.switchOff();
				txt[1].moveTo(500,230);
				Gl_hook('levelFinishedLoop()');
				if(++level==levelBarrier.length){
					speed++;
					level=0
				}
			}
			showGemsLeft()
			if(++levelPillPositionIX==(currentPillPosition.length/2))
				levelPillPositionIX=0;
			ship.hit.moveTo(currentPillPosition[levelPillPositionIX*2],currentPillPosition[(levelPillPositionIX*2)+1]);
		}
		if(convoyLength<35)
			convoyLength++;
	}else if(ship.hit.index<104){
		var d=Math.sqrt(Math.abs((ship.x+16)-(ship.hit.x+13))*Math.abs((ship.y+16)-(ship.hit.y+13)))
		if(d<10)
			loseLife();
	}else{
		var s=ship.hit;
		if(s.x+4>ship.x+32) return;
		if(s.y+4>ship.y+32) return;
		if(s.x+s.width-4<ship.x) return;
		if(s.y+s.height-4<ship.y) return;
		loseLife();
	}
}

function loseLife(){
	Gl_ticker=0;
	xplode.setFrame(0);
	xplode.moveTo(ship.x-16,ship.y-16);
	xplode.setAnimationSpeed(2,'Forward');
	xplode.switchOn();
	ship.setSpeed(0);
	ship.switchOff();
	Gl_unhook('gameLoop()');
	Gl_hook('lifeLostLoop()');
}

function lifeLostLoop(){
	drawConvoy();
	if(Gl_ticker>20){
		if(convoyLength==0||(convoy[convoyLength-1].x==ship.x&&convoy[convoyLength-1].y==ship.y)){
			Gl_unhook('lifeLostLoop()');
			if(--lives==-1){
				for(var n=0;n<convoyLength;n++)
					convoy[n].moveTo(-50,0);
				Gl_ticker=0;
				txt[2].setOpacity(0);
				txt[2].moveTo(170,230);
				xplode.switchOff();
				Gl_hook('gameOverLoop()');
			}else
				startLevel();
		}
	}
}

function levelFinishedLoop(){
	var tickd3=(Gl_ticker/4)-1;
	if(Gl_ticker%4==0){
		if(tickd3<convoyLength){
			convoy[tickd3].setFrame(16);
			convoy[tickd3].setXYdegs(0);
			convoy[tickd3].setSpeed(2);
			score+=50;
			showScore();
		}
		for(var n=0;n<convoyLength;n++){
			if(convoy[n].speed>0)
				convoy[n].setSpeed(convoy[n].speed+1);
		}
	}
	txt[1].moveTo(txt[1].x-10,txt[1].y);
	if(txt[1].x<-400&&convoy[convoyLength-1].y<-32){
		Gl_unhook('levelFinishedLoop()');
		Gl_ticker=0;
		startLevel();
	}
}

function gameOverLoop(){
	if(Gl_ticker<26)
		txt[2].setOpacity(Gl_ticker*4);
	if(Gl_ticker==40){
		Gl_unhook('gameOverLoop()');
		startDemo();
	}
}

</script>
</head>
<body bgcolor="#000000" onload="init()"></body>
</html>